package net.flashdan.managers.titlewindowmanager
{
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	
	import mx.core.IChildList;
	import mx.core.IMXMLObject;
	import mx.core.UIComponent;
	import mx.events.EffectEvent;
	import mx.events.FlexEvent;
	import mx.events.SandboxMouseEvent;
	
	import net.flashdan.containers.customtitlewindow.CustomTitleWindow;
	import net.flashdan.containers.customtitlewindow.CustomTitleWindowBoundsEvent;
	
	import spark.components.TitleWindow;
	import spark.effects.Move;
	import spark.events.TitleWindowBoundsEvent;
	import spark.primitives.Rect;

	
	public class TitleWindowManager implements IMXMLObject {
		
		private var _parent:UIComponent;
		private var windows:Dictionary = new Dictionary();
		private var _enforceBoundaries:Boolean = false;
		private var _softBoundaries:Boolean = false;
		private var _softBoundariesMoveEffect:Move;
		
		public var snapping:Boolean = false;
		public var snapDistance:Number = 8;
		public var tilePadding:Number = 5;
		public var snapToEdges:Boolean = true;
		

		public function TitleWindowManager() {
			
		}
		
		public function initialized(document:Object, id:String):void {
			_parent = document as UIComponent;
			_parent.addEventListener(FlexEvent.CREATION_COMPLETE, parentOnCreationComplete);
		}
		
		private function parentOnCreationComplete(event:FlexEvent):void {
			//listen for when title windows are added and removed
			_parent.systemManager.addEventListener(Event.ADDED, onAdded); //Is this expensive, since this will pick up almost everything?  Anyway to listen on systemManager.popUpChildren?
			_parent.systemManager.addEventListener(Event.REMOVED, onRemoved);
		}
		
		private function onAdded(event:Event):void {
			//This manager only manages TitleWindows and nothing else.
			if(event.target is TitleWindow){
				var win:TitleWindow = event.target as TitleWindow;
				
				//add this new title window to our managed windows list
				windows[win] = win;
				
				//add the window listeners needed by this manager
				addWindowListeners(win);
			}
		}
		
		private function onRemoved(event:Event):void {
			if(event.target is TitleWindow){
				//clean up the window listeners
				removeWindowListeners(event.target as TitleWindow);
				delete windows[event.target];
			}
		}
		
		private function addWindowListeners(win:TitleWindow):void {
			//Listener for snapping and enforcing hard boundaries.
			win.addEventListener(TitleWindowBoundsEvent.WINDOW_MOVING, onWindowMoving);
			
			//Listener for enforcing soft boundaries
			win.addEventListener(TitleWindowBoundsEvent.WINDOW_MOVE_END, onWindowMoveEnd);
			
			//If the window is a CustomTitleWindow, then listen for resize events.
			if(win is CustomTitleWindow){
				CustomTitleWindow(win).enforceBoundaries = false;// turn off so it does not interfere with this managers logic
				win.addEventListener(CustomTitleWindowBoundsEvent.WINDOW_RESIZING, onWindowResizing);
			}
		}
		
		private function removeWindowListeners(win:TitleWindow):void {
			win.removeEventListener(TitleWindowBoundsEvent.WINDOW_MOVING, onWindowMoving);
			win.removeEventListener(TitleWindowBoundsEvent.WINDOW_MOVE_END, onWindowMoveEnd);
			if(win is CustomTitleWindow){
				win.removeEventListener(CustomTitleWindowBoundsEvent.WINDOW_RESIZING, onWindowResizing);
			}
		}
		
		protected function onWindowMoving(event:TitleWindowBoundsEvent):void {
			var newAfterBounds:Rectangle = event.afterBounds.clone();
			var win:TitleWindow = event.target as TitleWindow;
			
			//check for snapping
			if(snapping){
				snapWindow(win, newAfterBounds);
			}
			
			//check for hard boundary limit
			if(enforceBoundaries && !softBoundaries){
				if (newAfterBounds.left < 0) {
					newAfterBounds.left = 0;
				} else if (newAfterBounds.right > _parent.systemManager.stage.stageWidth) {
					newAfterBounds.left = Math.max(0,_parent.systemManager.stage.stageWidth - newAfterBounds.width);
				}
				if (newAfterBounds.top < 0) {
					newAfterBounds.top = 0;
				} else if (newAfterBounds.bottom > _parent.systemManager.stage.stageHeight) {
					newAfterBounds.top = Math.max(0,_parent.systemManager.stage.stageHeight - newAfterBounds.height);
				}
			}
			
			//update the original afterBounds based on any modifications made to the newAfterBounds
			setAfterBoundProperties(event.afterBounds, newAfterBounds);
		}		
		
		protected function onWindowResizing(event:CustomTitleWindowBoundsEvent):void {
			if(snapping){
				var newAfterBounds:Rectangle = event.afterBounds.clone();
				snapWindow(event.target as TitleWindow, newAfterBounds, (event.target as CustomTitleWindow).currentResizeHandle);
				setAfterBoundProperties(event.afterBounds, newAfterBounds);
			}
		}
		
		//onWindowMoveEnd -> this is the logic that does the soft boundary logic
		protected function onWindowMoveEnd(event:TitleWindowBoundsEvent):void {
			if(enforceBoundaries && softBoundaries){
				var win:TitleWindow = event.target as TitleWindow;
				var xVal:Number = event.afterBounds.left;
				var yVal:Number = event.afterBounds.top;
				
				//Check to see if the window is out of bounds, and if it is, move it back on screen
				if (event.afterBounds.left < 0) {
					xVal = 0;
				} else if (event.afterBounds.right > _parent.systemManager.stage.stageWidth) {
					xVal = Math.max(0,_parent.systemManager.stage.stageWidth - event.afterBounds.width);
				}
				if (event.afterBounds.top < 0) {
					yVal = 0;
				} else if (event.afterBounds.bottom > _parent.systemManager.stage.stageHeight) {
					yVal = Math.max(0,_parent.systemManager.stage.stageHeight - event.afterBounds.height);
				}
				
				//Only move if the window was adjusted to move back into the screen
				if(xVal != event.afterBounds.left || yVal != event.afterBounds.top){
					if(softBoundariesMoveEffect){
						win.enabled = false;//disable the window while the effect plays
						softBoundariesMoveEffect.target = win;
						softBoundariesMoveEffect.xTo = xVal;
						softBoundariesMoveEffect.yTo = yVal;
						softBoundariesMoveEffect.addEventListener(EffectEvent.EFFECT_END, 
							function():void {
								win.enabled = true;
							}
						);
						softBoundariesMoveEffect.play();
					}
					else {
						//default move with no effect
						win.x = xVal;
						win.y = yVal;
					}
				}
			}
		}
		
		protected function setAfterBoundProperties(original:Rectangle, newAfterBounds:Rectangle):void {
			original.x = newAfterBounds.x; 
			original.y = newAfterBounds.y;
			original.width = newAfterBounds.width;
			original.height = newAfterBounds.height;
		}
		
		protected function snapWindow(dragWindow:TitleWindow, rect:Rectangle, dragHandle:String=null):void {
			var xDist:Number = NaN;                         
			var yDist:Number = NaN;                         
			var dragRect:Rectangle = getPaddedBounds(rect);
			
			// find the minimum snap (if one exists)                         
			for each(var window:TitleWindow in windows) {                                 
				if(window != dragWindow && dragRect.intersects(getPaddedBounds(rect))) { 
					if(!dragHandle || CustomTitleWindow.isLeft(dragHandle)) {                                                 
						xDist = calculateSnapDistance(rect.x, window.x + window.width + tilePadding, xDist);                                                 
						xDist = calculateSnapDistance(rect.x, window.x, xDist);                                         
					}                                          
					if(!dragHandle || CustomTitleWindow.isRight(dragHandle)) {                                                 
						xDist = calculateSnapDistance(rect.x + rect.width, window.x - tilePadding, xDist);                                                 
						xDist = calculateSnapDistance(rect.x + rect.width, window.x + window.width, xDist);                                         
					}                                          
					if(!dragHandle || CustomTitleWindow.isTop(dragHandle)) {                                                 
						yDist = calculateSnapDistance(rect.y, window.y + window.height + tilePadding, yDist);                                                 
						yDist = calculateSnapDistance(rect.y, window.y, yDist);                                         
					}                                          
					if(!dragHandle || CustomTitleWindow.isBottom(dragHandle)) {                                                 
						yDist = calculateSnapDistance(rect.y + rect.height, window.y - tilePadding, yDist);                                   
						yDist = calculateSnapDistance(rect.y + rect.height, window.y + window.height, yDist);                                         
					}                                 
				}                         
			} 
			
			//check if we should snap on the edges of the application
			if(snapToEdges){
				//left edge
				if(!dragHandle || CustomTitleWindow.isLeft(dragHandle)) { 
					xDist = calculateSnapDistance(rect.x, 0, xDist); 
				}
				//right edge
				if(!dragHandle || CustomTitleWindow.isRight(dragHandle)) {
					//xDist = calculateSnapDistance(rect.x + rect.width, window.x + window.width, xDist); 
					xDist = calculateSnapDistance(rect.x + rect.width, _parent.systemManager.stage.stageWidth, xDist);
				}
				//top edge
				if(!dragHandle || CustomTitleWindow.isTop(dragHandle)) {
					yDist = calculateSnapDistance(rect.y, 0, yDist ); 
				}
				//bottom edge
				if(!dragHandle || CustomTitleWindow.isBottom(dragHandle)) { 
					yDist = calculateSnapDistance(rect.y + rect.height, _parent.systemManager.stage.stageHeight, yDist);
				}
			}
			
			var xChanged:Boolean = !isNaN(xDist);                         
			var yChanged:Boolean = !isNaN(yDist);                          
			// update the x, y, width, height based on the user interaction                         
			// dragHandle contains either a MDIResizeHandle value, or null if the window is being dragged                         
			switch(dragHandle) {                                 
				case CustomTitleWindow.LEFT:                                         
					if(xChanged) {                                                 
						rect.x -= xDist;                                                 
						rect.width += xDist;                                         
					}                                 
					break;                                  
				case CustomTitleWindow.RIGHT:                                         
					if(xChanged )                                                 
						rect.width -= xDist;                                 
					break;                                  
				case CustomTitleWindow.TOP:      
					if(yChanged) {                                                 
						rect.y -= yDist;                                                 
						rect.height += yDist;                                         
					}                                 
					break;                                  
				case CustomTitleWindow.BOTTOM:                                         
					if(yChanged)                                                 
						rect.height -= yDist;                                 
					break;                                  
				case CustomTitleWindow.TOP_LEFT:                                         
					if(xChanged) {                                                 
						rect.x -= xDist;                                                 
						rect.width += xDist;                                         
					}                                         
					if(yChanged) {                                                 
						rect.y -= yDist;                                                 
						rect.height += yDist;                                         
					}                                 
					break;                                  
				case CustomTitleWindow.TOP_RIGHT:                                         
					if(xChanged)                                                 
						rect.width -= xDist;                                         
					if(yChanged) {                                                 
						rect.y -= yDist;                                                 
						rect.height += yDist;                                         
					}                                 
					break;                                  
				case CustomTitleWindow.BOTTOM_LEFT:                                         
					if(xChanged) {                                                 
						rect.x -= xDist;                                                 
						rect.width += xDist;                                         
					}                                         
					if(yChanged)                                                 
						rect.height -= yDist;                                 
					break;                                  
				case CustomTitleWindow.BOTTOM_RIGHT:                                         
					if(yChanged)                                                 
						rect.height -= yDist;                                         
					if(xChanged)                                                 
						rect.width -= xDist;                                 
					break;                                  
				default:                                         
					if(xChanged)                                                 
						rect.x -= xDist;                                         
					if(yChanged)                                                 
						rect.y -= yDist;                                 
					break;                         
			} 
		}
		
		protected function calculateSnapDistance(edge1:Number, edge2:Number, currentShift:Number):Number {                         
			var gap:Number = edge1 - edge2;                          
			// if we're within snapping range                         
			if(gap > -snapDistance && gap < snapDistance) {                                 
				// if this snap is shorter than the currentShift                                 
				if(isNaN(currentShift) || Math.abs(gap) < Math.abs(currentShift))  {                                 
					currentShift = gap;                         
				}   
			}
			                      
			return currentShift;                 
		} 
		
		protected function getPaddedBounds(rect:Rectangle):Rectangle {                         
			return new Rectangle(rect.x - tilePadding - snapDistance/2, 
								 rect.y - tilePadding - snapDistance/2,                                                                  
								 rect.width + tilePadding + snapDistance,                                                                  
								 rect.height + tilePadding + snapDistance 
						);                
		} 
		
		public function set enforceBoundaries(val:Boolean):void {
			if(val != _enforceBoundaries){
				_enforceBoundaries = val;
			}
		}
		public function get enforceBoundaries():Boolean {
			return _enforceBoundaries;
		}
		
		public function set softBoundaries(val:Boolean):void {
			if(val != _softBoundaries){
				_softBoundaries = val;
			}
		}
		public function get softBoundaries():Boolean {
			return _softBoundaries;
		}
		
		public function set softBoundariesMoveEffect(val:Move):void {
			_softBoundariesMoveEffect = val;
		}
		public function get softBoundariesMoveEffect():Move {
			return _softBoundariesMoveEffect;
		}
		
	}
}